using System;
using System.Collections.Generic;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Numerics;
using System.Text;
using System.Text.Json;

namespace awdb
{

    public class Awdb
    {
        private readonly MemoryMappedViewAccessor _byteData;
        private readonly uint _metadataLen;
        private readonly JsonElement _metadata;
        private readonly uint _nodeCount;
        private readonly string _ipVersion;
        private readonly uint _byteLen;
        private readonly List<dynamic> _columns;
        private readonly uint _startLen;
        private readonly uint _baseOffset;
        private readonly uint _decodeType;

        public Awdb(string fileName)
        {
            using (var dbFile = MemoryMappedFile.CreateFromFile(fileName))
            {
                this._byteData = dbFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
                this._metadataLen = (uint)this._byteData.ReadByte(0) << 8 | this._byteData.ReadByte(1);
                var metadataBytes = new byte[this._metadataLen];
                this._byteData.ReadArray(2, metadataBytes, 0, (int)_metadataLen);
                this._metadata = JsonSerializer.Deserialize<JsonElement>(metadataBytes);
                this._nodeCount = this._metadata.GetProperty("node_count").GetUInt32();
                this._ipVersion = this._metadata.GetProperty("ip_version").GetString();
                this._byteLen = this._metadata.GetProperty("byte_len").GetUInt32();
                this._decodeType = this._metadata.GetProperty("decode_type").GetUInt32();
                
                
                this._columns = new List<dynamic>();
                JsonElement columnsProperty = this._metadata.GetProperty("columns");
                foreach (JsonElement element in columnsProperty.EnumerateArray())
                {
                    if (element.ValueKind == JsonValueKind.Array)
                    {
                        var array = element.EnumerateArray().Select(e => (object)e).ToList();


                        this._columns.Add(array);
                    }
                    else
                    {
                        this._columns.Add(element.GetString());
                    }
                }

                this._startLen = 2 + this._metadataLen;
                this._baseOffset = this._nodeCount * this._byteLen * 2 + this._startLen;
            }
        }

        private static bool IsIPv4(string? ip)
        {
            if (IPAddress.TryParse(ip, out var address))
            {
                return address.AddressFamily == AddressFamily.InterNetwork;
            }

            return false;
        }

        private static bool IsIPv6(string? ip)
        {
            if (IPAddress.TryParse(ip, out var address))
            {
                return address.AddressFamily == AddressFamily.InterNetworkV6;
            }

            return false;
        }

        private static string IPv4MapIPv6(string ipv4Address)
        {
            var ipv4Bytes = IPAddress.Parse(ipv4Address).GetAddressBytes();
            var ipv6Bytes = new byte[16];
            ipv6Bytes[10] = 0xff;
            ipv6Bytes[11] = 0xff;
            Array.Copy(ipv4Bytes, 0, ipv6Bytes, 12, ipv4Bytes.Length);
            var ipv6Address = new IPAddress(ipv6Bytes);
            return ipv6Address.ToString();
        }
        public static long IPv4MappedIPv6ToInt(IPAddress ipAddress)
        {
            byte[] bytes = ipAddress.GetAddressBytes();
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }
            return BitConverter.ToInt64(bytes, 0);
        }
        public static bool IsIPv4MappedToIPv6(IPAddress ipAddress)
        {
            byte[] bytes = ipAddress.GetAddressBytes();

            // 检查地址是否以IPv4映射的IPv6前缀开始
            for (int i = 0; i < 10; i++)
            {
                if (bytes[i] != 0)
                {
                    return false;
                }
            }
            if (bytes[10] != 0xff || bytes[11] != 0xff)
            {
                return false;
            }

            // 检查IPv4地址是否合法
            for (int i = 12; i < 16; i++)
            {
                if (bytes[i] != 0)
                {
                    return true;
                }
            }
            return false;
        }
        private static BigInteger IPv6ToInt(string? ipv6Address)
        {
            IPAddress ipAddress = IPAddress.Parse(ipv6Address);
            if (IsIPv4MappedToIPv6(ipAddress))
            {
                long intValue = IPv4MappedIPv6ToInt(ipAddress);
                return intValue;
            }
            else
            {
                byte[] bytes = ipAddress.GetAddressBytes();
                Array.Resize(ref bytes, 16);

                // 拆分IPv6地址为更小的部分，转换为整数
                BigInteger result = 0;
                for (int i = 0; i < 16; i += 2)
                {
                    BigInteger part = (BigInteger)bytes[i] << 8 | bytes[i + 1];
                    result = result << 16 | part;
                }
                return result;
            }
        }

        private static int IPv4ToInt(string? ipv4Address)
        {
            var ipv4Bytes = IPAddress.Parse(ipv4Address).GetAddressBytes();
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(ipv4Bytes);
            }

            return BitConverter.ToInt32(ipv4Bytes, 0);
        }

        private static string IntToIPv4(uint intIp)
        {
            var packedIp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)intIp));
            return new IPAddress(packedIp).ToString();
        }

        public uint FindTreeIndex(object ipAddress)
        {
            if (this._ipVersion == "4_6")
            {
                if (ipAddress is int)
                {
                    if ((int)ipAddress < Math.Pow(2, 32))
                    {
                        ipAddress = IntToIPv4((uint)ipAddress);
                        ipAddress = IPv4MapIPv6(ipAddress as string);
                    }
                }
                else if (IsIPv4(ipAddress as string))
                {
                    ipAddress = IPv4MapIPv6(ipAddress as string);
                }
            }

            int ipCount;
            BigInteger intIp;
            if (ipAddress is int)
            {
                if (this._ipVersion == "4")
                {
                    if ((int)ipAddress > Math.Pow(2, 32))
                    {
                        throw new ArgumentException(
                            $"The database is IPv4 library, but you are using ipv6 queries! The IP is:{ipAddress}");
                    }

                    ipCount = 32;
                    intIp = (int)ipAddress;
                }
                else
                {
                    if ((int)ipAddress < Math.Pow(2, 32))
                    {
                        throw new ArgumentException(
                            $"The database is IPv6 library, but you are using ipv4 queries! The IP is:{ipAddress}");
                    }

                    ipCount = 128;
                    intIp = (int)ipAddress;
                }
            }
            else
            {
                if (IsIPv4(ipAddress as string))
                {
                    if (this._ipVersion == "4")
                    {
                        intIp = IPv4ToInt(ipAddress as string);
                        ipCount = 32;
                    }
                    else
                    {
                        throw new ArgumentException(
                            $"The database is IPv6 library, but you are using ipv4 queries! The IP is:{ipAddress}");
                    }
                }
                else if (IsIPv6(ipAddress as string))
                {
                    if (this._ipVersion is "6" or "4_6")
                    {
                        intIp = IPv6ToInt(ipAddress as string);
                        ipCount = 128;
                    }
                    else
                    {
                        throw new ArgumentException(
                            $"The database is IPv4 library, but you are using ipv6 queries! The IP is:{ipAddress}");
                    }
                }
                else
                {
                    throw new ArgumentException($"Invalid IP address {ipAddress}!");
                }
            }

            uint nodeIndex = 0;
            if (this._ipVersion == "4_6")
            {
                // 方法1：位移操作，是判断IPv4映射到IPv6地址的前32位是否为0xFFFF，IPv4映射到IPv6地址的前32位固定为0xFFFF
               
                if ((intIp >> 32) == 0xFFFF)
                {
                    nodeIndex = 96;
                }
            }
           
            for (int i = ipCount -(int)nodeIndex- 1; i >= 0; i--)
            {
                if (nodeIndex >= this._nodeCount)
                {
                    break;
                }
 
                BigInteger bitValue = (intIp >> i) & 1;

                BigInteger offset = nodeIndex * this._byteLen * 2 + (int)bitValue * this._byteLen + this._startLen;
        
                byte[] nodeByte = new byte[this._byteLen];
                this._byteData.ReadArray((long)offset, nodeByte, 0, nodeByte.Length);
                Array.Reverse(nodeByte);
                nodeIndex = BitConverter.ToUInt32(nodeByte, 0);
            }

            if (nodeIndex == this._nodeCount)
            {
                //throw new ArgumentException("Invalid node_index in search tree");
                Console.WriteLine("Invalid node_index in search tree");
                return 0;
            }

            if (nodeIndex > this._nodeCount)
            {
                return nodeIndex;
            }

            throw new ArgumentException("Invalid node_index in search tree");
            
        }
        
        public uint FindTreeIndexBak(object ipAddress)
        {
            if (this._ipVersion == "4_6")
            {
                if (ipAddress is int)
                {
                    if ((int)ipAddress < Math.Pow(2, 32))
                    {
                        ipAddress = IntToIPv4((uint)ipAddress);
                        ipAddress = IPv4MapIPv6(ipAddress as string);
                    }
                }
                else if (IsIPv4(ipAddress as string))
                {
                    ipAddress = IPv4MapIPv6(ipAddress as string);
                }
            }

            string binIp;
            uint nodeIndex;
            if (ipAddress is int)
            {
                if (this._ipVersion == "4")
                {
                    if ((int)ipAddress < Math.Pow(2, 32))
                    {
                        binIp = Convert.ToString((int)ipAddress, 2).PadLeft(32, '0');
           
                    }
                    else
                    {
                        throw new("The database is IPv4 library, but you are using ipv6 queries!");
                    }

                }
                else
                {
                    if ((int)ipAddress < Math.Pow(2, 32))
                    {
                        throw new("The database is IPv6 library, but you are using ipv4 queries!");
                    }
                    else
                    {
                        binIp = Convert.ToString((int)ipAddress, 2).PadLeft(128, '0');
                    }
                }
            }
            else
            {
                if (IsIPv4(ipAddress as string))
                {
                    if (this._ipVersion == "4")
                    {
                        ipAddress = IPv4ToInt(ipAddress as string);
                        binIp = Convert.ToString((int)ipAddress, 2).PadLeft(32, '0');
                    }
                    else
                    {
                        throw new("The database is IPv6 library, but you are using ipv4 queries!");
                    }
                }
                else if (IsIPv6(ipAddress as string))
                {
                    if (this._ipVersion is "6" or "4_6")
                    {
                        ipAddress = IPv6ToInt(ipAddress as string);
                        binIp = Convert.ToString((int)ipAddress, 2).PadLeft(128, '0');
                    }
                    else
                    {
                        throw new("The database is IPv4 library, but you are using ipv6 queries!");
                    }
                }
                else
                {
                    throw new($"Invalid IP address {ipAddress}");
                }
            }
            nodeIndex = 0;
            if (this._ipVersion == "4_6")
            {
                // 方法1：位移操作，是判断IPv4映射到IPv6地址的前32位是否为0xFFFF，IPv4映射到IPv6地址的前32位固定为0xFFFF
       
                if (((int)ipAddress >> 32) == 0xFFFF)
                {
                    nodeIndex = 96;
                }
            }
            uint i = nodeIndex;
            while (i < binIp.Length)
            {
                if (nodeIndex > this._nodeCount)
                {
                    return nodeIndex;
                }
                long offset = nodeIndex * this._byteLen * 2 + int.Parse(binIp[(int)i].ToString()) * this._byteLen + this._startLen;
                byte[] nodeByte = new byte[this._byteLen];
                this._byteData.ReadArray(offset, nodeByte, 0, nodeByte.Length);
                Array.Reverse(nodeByte);
                nodeIndex = BitConverter.ToUInt32(nodeByte, 0);
       
                i += 1;
                if (nodeIndex == this._nodeCount)
                {
                    return 0;
                }
                if (nodeIndex > this._nodeCount)
                {
                    return nodeIndex;
                }
            }
            throw new ArgumentException("Invalid node_index in search tree");
        }
        private dynamic DecodeArray(uint offset)
        {
            int dataLen = Convert.ToInt32(this._byteData.ReadByte(offset));
            offset += 1;
            List<dynamic> array = new List<dynamic>();
            for (int i = 0; i < dataLen; i++)
            {
                dynamic value = DecodeContent(offset);
                array.Add(value.Item1);
                offset = (uint)value.Item2;
            }
            return (array, offset);
        }
        private object DecodeDouble( uint offset)
        {
            int size = sizeof(double);
            byte[] bytes = new byte[size];
            this._byteData.ReadArray(offset, bytes, 0, size);
            Array.Reverse(bytes);
            double value = BitConverter.ToDouble(bytes, 0);
            return (value, offset + size);
            
        }
        public object DecodeFloat(uint offset)
        {
            uint tempOffset = offset + 4;
            byte[] buffer = new byte[sizeof(float)];
            this._byteData.ReadArray(offset, buffer, 0, buffer.Length);
            Array.Reverse(buffer);
            float value = BitConverter.ToSingle(buffer, 0);
            return (value,tempOffset);
        }
        public object DecodeInt(uint offset)
        {
            uint tempOffset = offset + 4;
            byte[] buffer = new byte[4];
            this._byteData.ReadArray(offset, buffer, 0, buffer.Length);
            Array.Reverse(buffer);
            int value = BitConverter.ToInt32(buffer, 0);
            return (value,tempOffset);
        }
        private object DecodeUint(uint offset)
        {
      
            uint dataLen = Convert.ToUInt32(this._byteData.ReadByte(offset));
            
            offset += 1;
            uint tempOffset = offset + dataLen;
            byte[] sizeBytes = new byte[dataLen];
            this._byteData.ReadArray(offset, sizeBytes, 0, sizeBytes.Length);
            Array.Reverse(sizeBytes);
            
            uint utf8String = DecodeToUInt(dataLen,sizeBytes);
           
            return (utf8String,tempOffset);
        }
        private object DecodeLongStr(uint offset)
        {
            
            uint size = this._byteData.ReadByte(offset);
            offset++;

            uint dataLen;
            if (size == 1)
            {
                dataLen = this._byteData.ReadByte(offset);
                offset++;
            }
            else
            {
                byte[] sizeBytes = new byte[size];
                this._byteData.ReadArray(offset, sizeBytes, 0, (int)size);
                Array.Reverse(sizeBytes);
                dataLen = BitConverter.ToUInt16(sizeBytes, 0);
                offset += size;
            }

            uint tempOffset = offset + dataLen;
            byte[] dataBytes = new byte[dataLen];
            this._byteData.ReadArray(offset, dataBytes, 0, (int)dataLen);
            string decodedStr = Encoding.UTF8.GetString(dataBytes);
           
            return (decodedStr,tempOffset);
        }
        private dynamic DecodeStr(uint offset)
        {
           
            uint dataLen = Convert.ToUInt32(this._byteData.ReadByte(offset));

            offset += 1;
            uint tempOffset = offset + dataLen;

            if (dataLen == 0)
            {
                return (string.Empty, tempOffset);
            }
            
            byte[] value = new byte[dataLen];
            this._byteData.ReadArray(offset, value, 0, value.Length);
            string utf8String = Encoding.UTF8.GetString(value);
            return (utf8String,tempOffset);
        }
        private uint DecodeToUInt(uint dataLen, byte[] byteData)
        {
            switch (dataLen)
            {
                case 1:
                    return byteData[0];
                case 2:
                    return (ushort)((byteData[1] << 8) | byteData[0]);
                case 3:
                    uint value = (uint)((byteData[0] << 16) | (byteData[1] << 8) | byteData[2]);
                    return value;
                case 4:
                    return BitConverter.ToUInt32(byteData, 0);
                case 5:
                    return BitConverter.ToUInt32(byteData, 0);
            }

            return 0;
        }
        private object DecodePointer(uint offset)
        {
            
            uint dataLen =  Convert.ToUInt32(this._byteData.ReadByte(offset));
           
            offset += 1;
            
            uint newoffset = offset + dataLen;
            byte[] sizeBytes = new byte[dataLen];

            this._byteData.ReadArray(offset, sizeBytes, 0, sizeBytes.Length);
           
            if (dataLen!=3)
            {
                Array.Reverse(sizeBytes);
            }
            uint buf = DecodeToUInt(dataLen,sizeBytes);
            uint pointer = _baseOffset + buf;
            dynamic value = DecodeContent(pointer);
            return (value, (int)newoffset);
        }
        private dynamic DecodeContent( uint offset)
        {
            uint dataType = Convert.ToUInt32(this._byteData.ReadByte(offset));
            uint newOffset = offset + 1;
            switch (dataType)
            {
                case 1:
                    return DecodeArray(newOffset);
                case 2:
                    return DecodePointer(newOffset);
                case 3:
                    return DecodeStr(newOffset);
                case 4:
                    return DecodeLongStr(newOffset);
                case 5:
                    return DecodeUint(newOffset);
                case 6:
                    return DecodeInt(newOffset);
                case 7:
                    return DecodeFloat(newOffset);
                case 8:
                    return DecodeDouble(newOffset);
                default:
                    throw new ArgumentException($"Unexpected data type number ({dataType}), please check!");

            }
        }
        public static Dictionary<string, dynamic> MapKeyValue(List<dynamic> keys, List<dynamic> values)
        {
            int count;
            Dictionary<string, dynamic> forepartDict;
            
            if (keys.Count == values.Count)
            {
                count = values.Count;

                for (int i = 0; i < count; i++)
                {
                    
                    if (values[i] is ValueTuple<string, uint>)
                    {
                        values[i] = values[i].Item1;
                    }
                }
                
                forepartDict= Enumerable.Range(0, count)
                    .ToDictionary(i => (string)keys[i], i => values[i]);
                
            }
            else{
                count = values.Count -1;
                for (int i = 0; i < count; i++)
                {
                    if (values[i] is ValueTuple<string, uint>)
                    {
                        values[i] = values[i].Item1;
                    }
                    
                }
                
                forepartDict= Enumerable.Range(0, count)
                    .ToDictionary(i => (string)keys[i], i => values[i]);

                var keysList = keys[^1];
                var valuesList = values[^1];
                string multiAreasName = (string)keys[^2];
                List<Dictionary<string, string>> endDict = new List<Dictionary<string, string>> {};
                foreach (var row in valuesList)
                {
                    Dictionary<string, string> dictionary = new Dictionary<string, string>();
                    for (int i = 0; i < keysList.Count; i++)
                    {
                        
                        if (row[i] is not string)
                        {
                            row[i] = row[i].Item1;
                        }
                      
                        dictionary[keysList[i].ToString()] = row[i];
                   
                    }
                    endDict.Add(dictionary);
                }

                forepartDict[multiAreasName] = endDict;
            }
            
            return forepartDict;
        }
        public dynamic ReadAwdb(object ipAddress)
        {
            uint nodeIndex = FindTreeIndex(ipAddress);
            if (nodeIndex == 0)
            {
                Dictionary<string, object> dic = new Dictionary<string, object>();
                return dic;
            }
            else {
                uint pointer = this._baseOffset + nodeIndex - this._nodeCount - 10;
                dynamic parse_func = null;
                if (this._decodeType == 1)
                {
                    parse_func = DecodeContentStructure(pointer);
                }
                else if (this._decodeType == 2)
                {
                    parse_func = DecodeContentDirect(pointer);
                }
                return parse_func;
            }
        }

        private object DecodeContentDirect(uint offset)
        {
            
            uint dataLen;
            byte[] sizeBytes = new byte[4];
            this._byteData.ReadArray(offset, sizeBytes, 0, sizeBytes.Length);
            Array.Reverse(sizeBytes);
            dataLen = BitConverter.ToUInt16(sizeBytes, 0);
            offset += 4;
            
            byte[] value = new byte[dataLen];
            this._byteData.ReadArray(offset, value, 0, value.Length);
            string[] tempList = Encoding.UTF8.GetString(value).Split('\t');;
            
            Dictionary<string, object> dic = new Dictionary<string, object>();

            for (int i = 0; i < tempList.Length; i++)
            {
                dic[_columns[i]] = tempList[i];
            }
            
            
            return dic;
        }

        private object DecodeContentStructure(uint offset)
        {
          
            dynamic result = DecodeContent(offset);
            dynamic content = result.Item1;
           

            content = MapKeyValue(this._columns, content);
            
            
            return content;
        }
    }
    
}
